//! Variable management system
//! 
//! This module provides comprehensive variable management with three-tier scoping
//! (global, session, step) compatible with HttpRunner's variable system.

use crate::error::{Error, Result};
use serde_json::Value;
use std::collections::HashMap;

/// Variable scope enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VariableScope {
    /// Global variables (config level)
    Global,
    /// Session variables (session level)
    Session,
    /// Step variables (step level)
    Step,
}

/// Variable manager with three-tier scoping
pub struct VariableManager {
    /// Global variables (from config)
    global_vars: HashMap<String, Value>,
    
    /// Session variables (from session context)
    session_vars: HashMap<String, Value>,
    
    /// Step variables (from current step)
    step_vars: HashMap<String, Value>,
    
    /// Environment variables cache
    env_vars: HashMap<String, String>,
    
    /// Variable access statistics
    stats: VariableStats,
}

/// Variable access statistics
#[derive(Debug, Clone, Default)]
pub struct VariableStats {
    /// Number of global variable accesses
    pub global_accesses: usize,
    
    /// Number of session variable accesses
    pub session_accesses: usize,
    
    /// Number of step variable accesses
    pub step_accesses: usize,
    
    /// Number of environment variable accesses
    pub env_accesses: usize,
    
    /// Variable resolution warnings
    pub warnings: Vec<String>,
}

/// Variable resolver for template processing
pub struct VariableResolver {
    /// Reference to variable manager
    manager: VariableManager,
    
    /// Resolution configuration
    config: ResolverConfig,
}

/// Variable resolver configuration
#[derive(Debug, Clone)]
pub struct ResolverConfig {
    /// Allow undefined variables
    pub allow_undefined: bool,
    
    /// Default value for undefined variables
    pub undefined_default: Option<Value>,
    
    /// Case sensitive variable names
    pub case_sensitive: bool,
    
    /// Maximum resolution depth
    pub max_depth: usize,
}

impl Default for ResolverConfig {
    fn default() -> Self {
        Self {
            allow_undefined: true,
            undefined_default: None,
            case_sensitive: true,
            max_depth: 10,
        }
    }
}

impl VariableManager {
    /// Create a new variable manager
    pub fn new() -> Self {
        Self {
            global_vars: HashMap::new(),
            session_vars: HashMap::new(),
            step_vars: HashMap::new(),
            env_vars: HashMap::new(),
            stats: VariableStats::default(),
        }
    }
    
    /// Set a global variable
    pub fn set_global_variable(&mut self, name: String, value: Value) {
        self.global_vars.insert(name, value);
    }
    
    /// Set multiple global variables
    pub fn set_global_variables(&mut self, variables: HashMap<String, Value>) {
        self.global_vars.extend(variables);
    }
    
    /// Set a session variable
    pub fn set_session_variable(&mut self, name: String, value: Value) {
        self.session_vars.insert(name, value);
    }
    
    /// Set multiple session variables
    pub fn set_session_variables(&mut self, variables: HashMap<String, Value>) {
        self.session_vars.extend(variables);
    }
    
    /// Set a step variable
    pub fn set_step_variable(&mut self, name: String, value: Value) {
        self.step_vars.insert(name, value);
    }
    
    /// Set multiple step variables
    pub fn set_step_variables(&mut self, variables: HashMap<String, Value>) {
        self.step_vars.extend(variables);
    }
    
    /// Get a variable with scope resolution (step > session > global)
    pub fn get_variable(&mut self, name: &str) -> Option<&Value> {
        // Check step variables first (highest priority)
        if let Some(value) = self.step_vars.get(name) {
            self.stats.step_accesses += 1;
            return Some(value);
        }
        
        // Check session variables
        if let Some(value) = self.session_vars.get(name) {
            self.stats.session_accesses += 1;
            return Some(value);
        }
        
        // Check global variables (lowest priority)
        if let Some(value) = self.global_vars.get(name) {
            self.stats.global_accesses += 1;
            return Some(value);
        }
        
        None
    }
    
    /// Get a variable from specific scope
    pub fn get_variable_from_scope(&mut self, name: &str, scope: VariableScope) -> Option<&Value> {
        match scope {
            VariableScope::Global => {
                self.stats.global_accesses += 1;
                self.global_vars.get(name)
            }
            VariableScope::Session => {
                self.stats.session_accesses += 1;
                self.session_vars.get(name)
            }
            VariableScope::Step => {
                self.stats.step_accesses += 1;
                self.step_vars.get(name)
            }
        }
    }
    
    /// Check if a variable exists
    pub fn has_variable(&self, name: &str) -> bool {
        self.step_vars.contains_key(name)
            || self.session_vars.contains_key(name)
            || self.global_vars.contains_key(name)
    }
    
    /// Remove a variable from all scopes
    pub fn remove_variable(&mut self, name: &str) {
        self.step_vars.remove(name);
        self.session_vars.remove(name);
        self.global_vars.remove(name);
    }
    
    /// Clear step variables
    pub fn clear_step_variables(&mut self) {
        self.step_vars.clear();
    }
    
    /// Clear session variables
    pub fn clear_session_variables(&mut self) {
        self.session_vars.clear();
    }
    
    /// Clear all variables
    pub fn clear_all_variables(&mut self) {
        self.global_vars.clear();
        self.session_vars.clear();
        self.step_vars.clear();
    }
    
    /// Get all variables merged (step overrides session overrides global)
    pub fn get_all_variables(&self) -> HashMap<String, Value> {
        let mut merged = HashMap::new();
        
        // Start with global variables
        merged.extend(self.global_vars.clone());
        
        // Override with session variables
        merged.extend(self.session_vars.clone());
        
        // Override with step variables
        merged.extend(self.step_vars.clone());
        
        merged
    }
    
    /// Get variables from specific scope
    pub fn get_variables_from_scope(&self, scope: VariableScope) -> &HashMap<String, Value> {
        match scope {
            VariableScope::Global => &self.global_vars,
            VariableScope::Session => &self.session_vars,
            VariableScope::Step => &self.step_vars,
        }
    }
    
    /// Get environment variable
    pub fn get_env_variable(&mut self, name: &str) -> Option<String> {
        // Check cache first
        if let Some(value) = self.env_vars.get(name) {
            self.stats.env_accesses += 1;
            return Some(value.clone());
        }
        
        // Get from environment
        if let Ok(value) = std::env::var(name) {
            self.env_vars.insert(name.to_string(), value.clone());
            self.stats.env_accesses += 1;
            Some(value)
        } else {
            None
        }
    }
    
    /// Export variables to environment
    pub fn export_to_env(&self, names: &[String]) -> Result<()> {
        for name in names {
            if let Some(value) = self.get_all_variables().get(name) {
                let env_value = match value {
                    Value::String(s) => s.clone(),
                    Value::Number(n) => n.to_string(),
                    Value::Bool(b) => b.to_string(),
                    _ => serde_json::to_string(value).unwrap_or_default(),
                };
                std::env::set_var(name, env_value);
            }
        }
        Ok(())
    }
    
    /// Get variable statistics
    pub fn stats(&self) -> &VariableStats {
        &self.stats
    }
    
    /// Reset statistics
    pub fn reset_stats(&mut self) {
        self.stats = VariableStats::default();
    }
    
    /// Get variable count by scope
    pub fn variable_count(&self, scope: VariableScope) -> usize {
        match scope {
            VariableScope::Global => self.global_vars.len(),
            VariableScope::Session => self.session_vars.len(),
            VariableScope::Step => self.step_vars.len(),
        }
    }
    
    /// Get total variable count
    pub fn total_variable_count(&self) -> usize {
        self.global_vars.len() + self.session_vars.len() + self.step_vars.len()
    }
}

impl VariableResolver {
    /// Create a new variable resolver
    pub fn new(manager: VariableManager) -> Self {
        Self {
            manager,
            config: ResolverConfig::default(),
        }
    }
    
    /// Create a new variable resolver with custom configuration
    pub fn with_config(manager: VariableManager, config: ResolverConfig) -> Self {
        Self { manager, config }
    }
    
    /// Resolve a variable name to its value
    pub fn resolve(&mut self, name: &str) -> Result<Option<Value>> {
        if let Some(value) = self.manager.get_variable(name) {
            Ok(Some(value.clone()))
        } else if self.config.allow_undefined {
            Ok(self.config.undefined_default.clone())
        } else {
            Err(Error::Internal(format!("Undefined variable: {}", name)))
        }
    }
    
    /// Get the underlying variable manager
    pub fn manager(&self) -> &VariableManager {
        &self.manager
    }
    
    /// Get mutable reference to the underlying variable manager
    pub fn manager_mut(&mut self) -> &mut VariableManager {
        &mut self.manager
    }
    
    /// Get resolver configuration
    pub fn config(&self) -> &ResolverConfig {
        &self.config
    }
    
    /// Set resolver configuration
    pub fn set_config(&mut self, config: ResolverConfig) {
        self.config = config;
    }
}

impl Default for VariableManager {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_variable_manager_creation() {
        let manager = VariableManager::new();
        assert_eq!(manager.total_variable_count(), 0);
        assert_eq!(manager.variable_count(VariableScope::Global), 0);
        assert_eq!(manager.variable_count(VariableScope::Session), 0);
        assert_eq!(manager.variable_count(VariableScope::Step), 0);
    }

    #[test]
    fn test_variable_scoping() {
        let mut manager = VariableManager::new();
        
        // Set variables in different scopes
        manager.set_global_variable("var1".to_string(), Value::String("global".to_string()));
        manager.set_session_variable("var1".to_string(), Value::String("session".to_string()));
        manager.set_step_variable("var1".to_string(), Value::String("step".to_string()));
        
        // Step variable should have highest priority
        assert_eq!(manager.get_variable("var1"), Some(&Value::String("step".to_string())));
        
        // Clear step variables
        manager.clear_step_variables();
        
        // Session variable should now be returned
        assert_eq!(manager.get_variable("var1"), Some(&Value::String("session".to_string())));
        
        // Clear session variables
        manager.clear_session_variables();
        
        // Global variable should now be returned
        assert_eq!(manager.get_variable("var1"), Some(&Value::String("global".to_string())));
    }

    #[test]
    fn test_variable_operations() {
        let mut manager = VariableManager::new();
        
        // Test setting and getting variables
        manager.set_global_variable("name".to_string(), Value::String("John".to_string()));
        manager.set_global_variable("age".to_string(), Value::Number(30.into()));
        manager.set_global_variable("active".to_string(), Value::Bool(true));
        
        assert!(manager.has_variable("name"));
        assert!(manager.has_variable("age"));
        assert!(manager.has_variable("active"));
        assert!(!manager.has_variable("nonexistent"));
        
        // Test getting all variables
        let all_vars = manager.get_all_variables();
        assert_eq!(all_vars.len(), 3);
        assert_eq!(all_vars["name"], Value::String("John".to_string()));
        
        // Test removing variables
        manager.remove_variable("age");
        assert!(!manager.has_variable("age"));
        assert_eq!(manager.total_variable_count(), 2);
    }

    #[test]
    fn test_environment_variables() {
        let mut manager = VariableManager::new();
        
        // Set test environment variable
        std::env::set_var("TEST_ENV_VAR", "test_value");
        
        // Get environment variable
        let env_value = manager.get_env_variable("TEST_ENV_VAR");
        assert_eq!(env_value, Some("test_value".to_string()));
        
        // Test caching
        let env_value2 = manager.get_env_variable("TEST_ENV_VAR");
        assert_eq!(env_value2, Some("test_value".to_string()));
        
        // Clean up
        std::env::remove_var("TEST_ENV_VAR");
    }

    #[test]
    fn test_variable_resolver() {
        let mut manager = VariableManager::new();
        manager.set_global_variable("test_var".to_string(), Value::String("test_value".to_string()));
        
        let mut resolver = VariableResolver::new(manager);
        
        // Test resolving existing variable
        let result = resolver.resolve("test_var").unwrap();
        assert_eq!(result, Some(Value::String("test_value".to_string())));
        
        // Test resolving non-existing variable
        let result = resolver.resolve("nonexistent").unwrap();
        assert_eq!(result, None);
    }

    #[test]
    fn test_variable_statistics() {
        let mut manager = VariableManager::new();
        manager.set_global_variable("global_var".to_string(), Value::String("value".to_string()));
        manager.set_session_variable("session_var".to_string(), Value::String("value".to_string()));
        manager.set_step_variable("step_var".to_string(), Value::String("value".to_string()));
        
        // Access variables to generate statistics
        manager.get_variable("global_var");
        manager.get_variable("session_var");
        manager.get_variable("step_var");
        
        let stats = manager.stats();
        assert_eq!(stats.global_accesses, 1);
        assert_eq!(stats.session_accesses, 1);
        assert_eq!(stats.step_accesses, 1);
    }
}
